/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
 
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "DiskStorage.h"
//#define _DEBUG_


static string fileescape(string s)
{
  int i;
  for (i = 0; s[i]; i++) {
    switch (s[i]) {
    case '/':
    case '\\':
    case '*':
    case '?':
    case '[':
    case ']':
    case '{':
    case '}':
      s[i] = '&';
      break;
    }
  }
  return s;
}


DiskStorage::DiskStorage()
{
}


DiskStorage::~DiskStorage()
{
}


int DiskStorage::init(string pdirectory)
{
  directory = pdirectory;
  // Check whether the given directory does already exist and create it
  // otherwise.
  struct stat filestat;
  if (stat(directory.c_str(), &filestat) == -1
    || S_ISDIR(filestat.st_mode) == 0)
    if (mkdir(directory.c_str(), S_IRWXU) == -1)
      return STORAGE_ERROR_MAKEDIR_FAILED;
  return 0;
}


int DiskStorage::load_all(void)
{
#ifdef _DEBUG_
  printf("DiskStorage::load_all(): Called.\n");
#endif
  return load_all_recursive(directory);
}


int DiskStorage::load_all_recursive(string directory)
{
#ifdef _DEBUG_
  printf("DiskStorage::load_all_recursive(): %s\n", directory.c_str());
#endif
  DIR*           stream     = NULL;
  struct dirent* filestruct = NULL;
  struct stat    filestat;
  string         fullfilename;
  
  // Recursively walk through all files in the current directory and load
  // every valid book.
  if (!(stream = opendir(directory.c_str()))) {     // Open the directory.
    printf("GtkFileList::update_hard(): Path open failed. %s\n",
           directory.c_str());
    return STORAGE_ERROR_DIR_OPEN_FAILED;
  }
  
  while ((filestruct = readdir(stream)) != NULL) {  // Walk through all files.
    if (strcmp(filestruct->d_name, ".") == 0
      || strcmp(filestruct->d_name, "..") == 0)
      continue;
    
    // Create a full path.
    fullfilename = directory + filestruct->d_name;
    
    // Stat the file.
    if (stat(fullfilename.c_str(), &filestat) == -1)
      continue;
    
    // If it is a directory, recurse.
    if (S_ISDIR(filestat.st_mode) != 0) {
      load_all_recursive(fullfilename + "/");
      continue;
    }
    
    // Make sure that the filename ends with ".book".
    //FIXME!!
    
    // Load the file.
    Book *book = new Book;
    load_book(fullfilename, book);
  }
  
  closedir(stream);
  
  return 0;
}


int DiskStorage::save_book(Book* book)
{
  try {
    xmlpp::Document xml;
    
    // Declare the namespace and uses its prefix for this node.
    xmlpp::Element* rootnode = xml.create_root_node("bookroot",
                                                    "http://www.debain.org/",
                                                    "book");
    
    // Add the author.
    xmlpp::Element* elem = rootnode->add_child("author");
    elem->set_child_text(book->get_author());
    
    // Add the book title.
    elem = rootnode->add_child("title");
    elem->set_child_text(book->get_title());
    
    // Add the isbn number.
    elem = rootnode->add_child("isbn");
    elem->set_child_text(book->get_isbn());
    
    // Add the category name.
    elem = rootnode->add_child("category");
    elem->set_child_text(book->get_category());
    
    // Add the summary.
    elem = rootnode->add_child("summary");
    elem->set_child_text(book->get_summary());
    
    // Add the review.
    elem = rootnode->add_child("review");
    elem->set_child_text(book->get_review());
    
    // Add the rating.
    elem = rootnode->add_child("rating");
    char rating[3];
    snprintf(rating, 3, "%i", book->get_rating());
    elem->set_child_text(rating);
    
    // Add the readdate.
    struct tm date = book->get_readdate();
    struct tm undefined;
    memset(&undefined, 0, sizeof(tm));
    if (memcmp(&date, &undefined, sizeof(tm)) != 0) {
      char year[5], month[3], day[3];
      snprintf(year,  5, "%i", date.tm_year + 1900);
      snprintf(month, 3, "%i", date.tm_mon);
      snprintf(day,   3, "%i", date.tm_mday);
      elem = rootnode->add_child("readdate");
      elem->set_attribute("year",  year);
      elem->set_attribute("month", month);
      elem->set_attribute("day",   day);
    }
    
    // Generate a directory for the file (if necessary).
    string subdir = directory
                  + fileescape(book->get_author())
                  + "/";
    struct stat filestat;
    if (stat(subdir.c_str(), &filestat) == -1
      || S_ISDIR(filestat.st_mode) == 0)
      if (mkdir(subdir.c_str(), S_IRWXU) == -1)
        return STORAGE_ERROR_MAKEDIR_FAILED;
    
    // Generate the filename from the book title.
    string filename = subdir
                    + fileescape(book->get_title())
                    + ".book";
    
    // Write do disk.
    xml.write_to_file(filename);
    
    // If the file has been saved using a different name last time, remove the
    // old file now.
    if (book->get_filename() != ""
      && book->get_filename() != filename) {
      if (remove(book->get_filename().c_str()) == -1) {
        book->set_filename(filename);
        return STORAGE_ERROR_FILE_DELETE_FAILED;
      }
    }
    
    book->set_filename(filename);
  }
  catch(const exception& ex) {
    cout << "Exception caught: " << ex.what() << endl;
    return STORAGE_ERROR_XML_EXCEPTION;
  }
  
  return 0;
}


int DiskStorage::delete_book(Book* book)
{
  if (remove(book->get_filename().c_str()) == -1)
    return STORAGE_ERROR_FILE_DELETE_FAILED;
  return 0;
}


int DiskStorage::load_book(string filename, Book* book)
{
#ifdef _DEBUG_
  printf("DiskStorage::load_book(): Loading %s\n", filename.c_str());
#endif
  xmlpp::DomParser parser;
  
  try {
    //parser.set_validate();
    parser.set_substitute_entities();  // Automatically resolve/unescape text.
    parser.parse_file(filename);
  }
  catch(const exception& ex) {
    cout << "Exception caught: " << ex.what() << endl;
    return STORAGE_ERROR_XML_EXCEPTION;
  }
  
  if (!parser)
    return STORAGE_ERROR_XML;
  
  // Ok, so everything worked fine.
  // Copy the data from the tree into the book.
  book->set_filename(filename);
  const xmlpp::Node* xml_root = parser.get_document()->get_root_node();
  int err = xml2book(xml_root, book);
  signal_book_loaded.emit(book);
  return err;
}


int DiskStorage::xml2book(const xmlpp::Node* node, Book* book)
{
  const xmlpp::Element*     elem = dynamic_cast<const xmlpp::Element*>(node);
  const xmlpp::ContentNode* contentnode =
                                  dynamic_cast<const xmlpp::ContentNode*>(node);
  
#ifdef _DEBUG_
  if (contentnode)
    cout << node->get_path() + ": " + contentnode->get_content() << endl;
#endif
  
  if (contentnode
    && contentnode->get_path() == "/book:bookroot/author/text()")
    book->set_author(contentnode->get_content());
  else if (contentnode
    && contentnode->get_path() == "/book:bookroot/title/text()")
    book->set_title(contentnode->get_content());
  else if (contentnode
    && contentnode->get_path() == "/book:bookroot/isbn/text()")
    book->set_isbn(contentnode->get_content());
  else if (contentnode
    && contentnode->get_path() == "/book:bookroot/category/text()")
    book->set_category(contentnode->get_content());
  else if (contentnode
    && contentnode->get_path() == "/book:bookroot/summary/text()")
    book->set_summary(contentnode->get_content());
  else if (contentnode
    && contentnode->get_path() == "/book:bookroot/review/text()")
    book->set_review(contentnode->get_content());
  else if (contentnode
    && contentnode->get_path() == "/book:bookroot/rating/text()")
    book->set_rating(atoi(contentnode->get_content().c_str()));
  else if (elem
    && elem->get_path() == "/book:bookroot/readdate") {
    xmlpp::Attribute* att_year  = elem->get_attribute("year");
    xmlpp::Attribute* att_month = elem->get_attribute("month");
    xmlpp::Attribute* att_day   = elem->get_attribute("day");
    string year  = att_year->get_value();
    string month = att_month->get_value();
    string day   = att_day->get_value();
    struct tm date;
    memset(&date, 0, sizeof(tm));
    date.tm_year = atoi(year.c_str());
    date.tm_mon  = atoi(month.c_str());
    date.tm_mday = atoi(day.c_str());
#ifdef _DEBUG_
    printf("Date: %i/%i/%i\n", date.tm_year, date.tm_mon, date.tm_mday);
#endif
    book->set_readdate(date.tm_year, date.tm_mon, date.tm_mday);
  }
  
  // Walk through all child nodes (recurse).
  xmlpp::Node::NodeList list = node->get_children();
  for (xmlpp::Node::NodeList::iterator iter = list.begin();
       iter != list.end();
       iter++)
    xml2book(*iter, book);
  
  return 0;
}
